/*
This file contains the implementation of the Graph class member functions.
The Graph class contains the data structure and the algorithms related to
known i.i.d. with integral types input model. See comments below, as well as
in the header file.

Copyright (C) 2018  Denis Pankratov

Modifications Copyright (C) 2019 Christodoulos Karavasilis

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#include "stdafx.h"
#include <vector>
#include <map>
#include <algorithm>
#include <string>
#include <iostream>
#include <queue>
#include "assert.h"
#include <random>
#include "graph.h"
#include "glpk.h" // for Brubach
#include <cmath>
#include <stack>
#include "bmcommon.h"
using namespace std;


/*
Returns a vector of length m, where each entry is a random number
between 0 and n.
*/
vector<int> iid2(int n, int m) {
	vector<int> result;
	uniform_int_distribution<int> uni(0, n - 1);
	for (int i = 0; i < m; i++) {
		result.push_back(uni(rng2));
	}
	return result;
}

/*
Constructor of a graph.

n - total number of vertices. More vertices can be added later.
*/
Graph::Graph(int n) {
	for (int i = 0; i < n; i++)
		addVertex();
}

/*
Adds a new vertex.
*/
void Graph::addVertex() {
	adj.push_back(vector<int>());
}

/*
Adds a new edge. Does not check for duplicate edges.

i - one endpoint of the edge
j - another endpoint of the edge

REQUIREMENT: 0 <= i,j < total number of vertices
*/
void Graph::addEdge(int i, int j) {
	assert(i >= 0 && i < adj.size() && j >= 0 && j < adj.size());
	adj[i].push_back(j);
	adj[j].push_back(i);
}

/*
Sets the size of the online side. MUST BE CALLED FOR BIPARTITE GRAPHS.
THE EXTRA ASSUMPTION is that edges only go between LHS and RHS -- not checked 
excplicitely.

CONVENTION: i such that (0 <= i < leftSize) corresponds to the LHS vertex.
i such that (leftSize <= i < total number of vertices) corresponds to the RHS 
vertex.
*/
void Graph::setLeftSize(int leftSize) {
	left_sz = leftSize;
}

/*
Returns the size of the LHS.
*/
int Graph::getLeftSize() const {
	return left_sz;
}

/*
Returns the total number of vertices.
*/
int Graph::getNumVertices() const {
	return adj.size();
}

/*
Returns the total number of edges.
*/
int Graph::getNumEdges() const {
	int res = 0;
	for (int i = 0; i < (int)adj.size(); i++)
		res += adj[i].size();
	return res / 2;
}

/*
Writes the graph to the output stream in the DIMACS format.
*/
void Graph::write(ostream out) {
	out << "c bipartite " << left_sz << endl;
	out << "p " << adj.size() << " " << getNumEdges() << endl;
	for (int i = 0; i < (int)adj.size(); i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			if (i < adj[i][j]) {
				out << "e " << i << " " << adj[i][j] << endl;
			}
		}
	}
}

/*
Sorts adjacency lists corresponding to online nodes according to permutation 
perm

perm - permutation of ALL VERTICES.

REQUIREMENT:  perm does not mix offline and online nodes
*/
void Graph::sortOffline(const vector<int> &perm) {
	assert(perm.size() == adj.size());

	// record rankings
	vector<int> inverse(perm.size(), -1);
	for (int i = 0; i < (int)perm.size(); i++)
		inverse[perm[i]] = i;

	auto myFunc = [&inverse](int i, int j) { return inverse[i] < inverse[j]; };

	for (int i = 0; i < left_sz; i++) {
		sort(adj[i].begin(), adj[i].end(), myFunc);
	}
}

vector<int> Graph::randomRanking() {
	vector<int> perm(adj.size(), 0);
	for (int i = 0; i < (int)adj.size(); i++)
		perm[i] = i;
	random_shuffle(perm.begin() + left_sz, perm.end());
	return ranking(perm);
}

/*
Returns a vector of size left_sz such that position i says which
offline node is matched with i.

perm is a permutation of nodes such that
perm[0 .. left_sz-1] is a permutation of the left hand side, and
perm[left_sz .. adj.size()-1] is a permutation of the right hand side

Runs ranking
*/
vector<int> Graph::ranking(const vector<int>& perm) {
	int n = adj.size();
	vector<int> online(left_sz, -1);
	vector<int> offline(n - left_sz, -1);
	sortOffline(perm);

	for (int i = 0; i < left_sz; i++) {
		int online_node = perm[i];

		for (int j = 0; j < (int)adj[online_node].size(); j++) {
			int offline_node = adj[online_node][j];
			if (offline[offline_node - left_sz] == -1) {
				online[online_node] = offline_node;
				offline[offline_node - left_sz] = online_node;
				break;
			}
		}
	}
	return online;
}

/*
Runs the Mindegree algorithm
*/
vector<int> Graph::minDegree(){
    int n = adj.size();
	vector<int> online(left_sz, -1);
	vector<int> offline(n - left_sz, -1);
	vector<int> off_degree(n - left_sz, 0);

	for (int i = 0; i < left_sz; i++) {

		for (int j = 0; j < (int)adj[i].size(); j++)
			off_degree[adj[i][j] - left_sz]++;

		int min_free_node = -1;
		int min_free_degree = adj.size();
		for (int j = 0; j < (int)adj[i].size(); j++){
			int offline_node = adj[i][j];
			if((offline[offline_node - left_sz] == -1) && (off_degree[offline_node - left_sz] < min_free_degree)){
				min_free_node = offline_node;
				min_free_degree = off_degree[offline_node - left_sz];
			}
		}
		if(min_free_node != -1){
			online[i] = min_free_node;
			offline[min_free_node - left_sz] = i;
		}
	}
	return online;
}

/*
Karp-Sipser algorithm
*/
vector<int> Graph::karp_sipser(){
    int n = adj.size();
	vector<int> online(left_sz, -1);
	vector<int> offline(n - left_sz, -1);
	vector<int> off_degree(n - left_sz, 0);


	for (int i = 0; i < left_sz; i++) {

		for (int j = 0; j < (int)adj[i].size(); j++)
			off_degree[adj[i][j] - left_sz]++;

		int matched_node = -1;
		vector<int> free_nodes;
		for (int j = 0; j < (int)adj[i].size(); j++){
			int offline_node = adj[i][j];
			if(offline[offline_node - left_sz] == -1){
				if(off_degree[offline_node - left_sz] == 1){
					matched_node = offline_node;
					break;
				} else
					free_nodes.push_back(offline_node);
			}
		}

		if((matched_node == -1) && (free_nodes.size() > 0))
			matched_node = free_nodes[rand() % free_nodes.size()];

		if(matched_node != -1){
			online[i] = matched_node;
			offline[matched_node - left_sz] = i;
		}
	}
	return online;
}

/*
Runs a simple greedy algorithm. Same as ranking with identity permutation.
*/
vector<int> Graph::greedy() {
	vector<int> id_perm(adj.size(), -1);
	for (int i = 0; i < (int)adj.size(); i++) id_perm[i] = i;
	return ranking(id_perm);
}

/*
Runs a second pass of Durr et al. Accepts results of first pass as the input.
*/
vector<int> Graph::secondPass(vector<int> & firstPass) {
	vector<int> result(left_sz, -1);
	vector<int> offline((int)adj.size() - left_sz, -1);
	vector<int> offlineFirstPass((int)adj.size() - left_sz, -1);

	for (int i = 0; i < left_sz; i++) {
		if (firstPass[i] != -1)
			offlineFirstPass[firstPass[i] - left_sz] = i;
	}

	for (int i = 0; i < left_sz; i++) {
		int best = -1;
		for (int j = 0; j < (int)adj[i].size(); j++) {
			int nb = adj[i][j];
			if (offline[nb - left_sz] == -1) {
				if (best == -1 || (offlineFirstPass[best - left_sz] != -1 && 
					offlineFirstPass[nb - left_sz] == -1)) {
					best = nb;
				}
			}
		}
		if (best != -1) {
			result[i] = best;
			offline[best - left_sz] = i;
		}
	}

	return result;
}


/*
Runs a third pass of Borodin et al. Accepts results of first pass and second 
as the input.
*/
vector<int> Graph::thirdPass(const vector<int> & firstPass, 
	const vector<int> &secPass) {
	vector<int> result(left_sz, -1);
	vector<int> offline((int)adj.size() - left_sz, -1);
	vector<int> offlineFirstPass((int)adj.size() - left_sz, -1);
	vector<int> offlineSecondPass((int)adj.size() - left_sz, -1);

	for (int i = 0; i < left_sz; i++) {
		if (secPass[i] != -1) {
			offlineSecondPass[secPass[i] - left_sz] = i;
		}
		if (firstPass[i] != -1) {
			offlineFirstPass[firstPass[i] - left_sz] = i;
		}
	}

	for (int i = 0; i < left_sz; i++) {
		int best = -1;
		for (int j = 0; j < (int)adj[i].size(); j++) {
			int nb = adj[i][j];
			if (offline[nb - left_sz] == -1) {
				if (best == -1) {
					best = nb;
				}
				if (offlineFirstPass[nb - left_sz] == -1 && 
					offlineSecondPass[nb - left_sz] == -1) {
					best = nb;
					break;
				}
				if (offlineFirstPass[best - left_sz] != -1 && 
					offlineFirstPass[nb - left_sz] == -1) {
					best = nb;
				}
			}
		}
		if (best != -1) {
			result[i] = best;
			offline[best - left_sz] = i;
		}
	}

	return result;
}

/*
Prints the graph to standard error for debugging purposes.
*/
void Graph::printDebug() {
	for (int i = 0; i < left_sz; i++) {
		cerr << i << ": ";
		for (int j = 0; j < (int)adj[i].size(); j++)
			cerr << adj[i][j] << " ";
		cerr << endl;
	}
}

/*
Finds an augmenting path in the graph for a given matching. The matching is 
given by two arrays: online and offline.

online[] is of size left_sz and online[i] is the index of an offline vertex 
such that i is matched to it, or -1 if i is not matched.
offline[] is of size adj.size-left_sz and offline[i] is the index of an online
vertex such that left_sz + i is matched to it, or -1 if left_sz + i is not 
matched.

The two arrays should be compatible.
*/
vector<int> Graph::find_augmenting_path(const vector<int>& online, 
	const vector<int> &offline) {
	for (int s = 0; s < left_sz; s++) {
		if (online[s] != -1)
			continue;
		vector<int> pred(adj.size(), -1);
		queue<int> q;
		q.push(s);
		pred[s] = s;
		int end = -1;
		int predend = -1;
		while (!q.empty() && end == -1) {
			int from = q.front();
			q.pop();
			for (int i = 0; i < (int)adj[from].size(); i++) {
				int to = adj[from][i];
				int match = offline[to - left_sz];
				if (match == -1) {
					end = to;
					predend = from;
					break;
				}
				else if (pred[match] == -1) {
					pred[match] = from;
					q.push(match);

				}
			}
		}
		if (end != -1) {
			vector<int> res;
			res.push_back(end);
			do {
				res.insert(res.begin(), predend);
				if (online[predend] != -1) {
					res.insert(res.begin(), online[predend]);
					predend = pred[predend];
				}
				else {
					break;
				}
			} while (true);

			return res;
		}
	}
	return vector<int>();
}

/*
Modify the matching specified by online and offline arrays via an
augmenting path specified in path.
*/
void Graph::augment(vector<int> & online, vector<int> &offline, 
	const vector<int>& path) {
	assert(path.size() % 2 == 0);

	for (int i = 0; i < (int)path.size(); i += 2) {
		online[path[i]] = path[i + 1];
		offline[path[i + 1] - left_sz] = path[i];
	}
}

/*
Compute maximum matching by running augmenting paths algorithm from a given
starting solution specified by array online. The answer is stored in the same 
array.
*/
void Graph::maxMatching(vector<int> & online) {
	vector<int> offline((int)adj.size() - left_sz, -1);
	for (int i = 0; i < left_sz; i++) {
		if (online[i] != -1) {
			offline[online[i] - left_sz] = i;
		}
	}
	vector<int> path = find_augmenting_path(online, offline);
	while (path.size() != 0) {
		augment(online, offline, path);
		path = find_augmenting_path(online, offline);
	}

}

/*
This function runs max flow algorithm on the flow network given by adjacency
matrix from s to t. The result is stored in the flow variable.
*/
void Graph::maxFlow(const vector<vector<int> > &adj, 
	map<pair<int, int>, int> &cap, map<pair<int, int>, int> &flow,
	int s, int t) {
	// run augmenting path algorithm
	do {
		vector<int> prev(adj.size(), -1);
		prev[s] = s;
		queue<int> q;
		q.push(s);
		bool found = false;
		while (!q.empty() && !found) { //find augmenting path
			int from = q.front();
			q.pop();
			for (int i = 0; i < (int)adj[from].size(); i++) {
				int to = adj[from][i];
				pair<int, int> edge = make_pair(from, to);
				if (prev[to] == -1 && (cap[edge] - flow[edge] > 0)) {
					prev[to] = from;
					q.push(to);
					if (to == t) {
						found = true;
						break;
					}
				}
			}
		}
		if (found) { //augment
			int cur = t;
			do {
				pair<int, int> edge = make_pair(prev[cur], cur);
				pair<int, int> revedge = make_pair(cur, prev[cur]);
				flow[edge]++;
				flow[revedge]--;
				cur = prev[cur];
			} while (prev[cur] != cur);
		}
		else {
			break;
		}
	} while (true);

}

/*
adj3 is an adjacency matrix of a bipartite graph consisting of cycles and paths,
where left is the number of vertices on the left hand side -- vertices are 
numbered from 0 to left. The algorithm decomposes the given cycles and paths 
according to Feldman et al. procedure and stores the two resulting matchings 
into blue and red input variables.
*/
void Graph::decomposeByFeldman(const vector<vector<int> > &adj3, int left,
	vector<int> &blue, vector<int> &red) {
	vector<int> pred(adj.size(), -1);

	for (int i = 0; i < left; i++) {
		if (pred[i] == -1 && adj3[i].size() != 0) {
			int cur = adj3[i][0]; // go down one branch
			pred[cur] = i;
			int len = 2;
			while (adj3[cur].size() == 2 && cur != i) {
				int next = (adj3[cur][0] == pred[cur]) ? adj3[cur][1] : 
					adj3[cur][0];
				pred[next] = cur;
				cur = next;
				len++;
			}

			if (cur == i) { // cycle!
				int st = i;
				do {
					blue[st] = pred[st];
					red[pred[pred[st]]] = pred[st];
					st = pred[pred[st]];
				} while (st != i);
			}
			else {
				// go down the other branch
				if (adj3[i].size() == 2) {
					int prev = i;
					int ncur = adj3[i][1];
					pred[prev] = ncur;
					len++;
					while (adj3[ncur].size() == 2) {
						int next = (adj3[ncur][0] == prev) ? adj3[ncur][1] : 
							adj3[ncur][0];
						pred[ncur] = next;
						prev = ncur;
						ncur = next;
						len++;
					}
					pred[ncur] = ncur;
				}
				else {
					pred[i] = i;
				}

				if (len % 2 == 0) { // number of nodes on the path is even, 
					                // i.e. length is odd
					if (cur >= left) {
						blue[pred[cur]] = cur;
						int st = pred[cur];
						while (pred[st] != st) {
							red[st] = pred[st];
							blue[pred[pred[st]]] = pred[st];
							st = pred[pred[st]];
						}
					}
					else {
						blue[cur] = pred[cur];
						int st = pred[cur];
						while (pred[st] != st) {
							red[pred[st]] = st;
							blue[pred[st]] = pred[pred[st]];
							st = pred[pred[st]];
						}
					}
				}
				else {
					if (cur >= left) { // even, starting on offline side
						int st = cur;
						do {
							blue[pred[st]] = st;
							red[pred[st]] = pred[pred[st]];
							st = pred[pred[st]];
						} while (pred[st] != st);
					}
					else { // even, starting on online side
						int st = cur;
						blue[st] = pred[st];
						blue[pred[pred[st]]] = pred[st];
						st = pred[pred[st]];
						while (pred[st] != st) {
							red[st] = pred[st];
							blue[pred[pred[st]]] = pred[st];
							st = pred[pred[st]];
						}
					}
				}
			}
		}
	}
}

/*
This function is supposed to be run on a type graph.
Recall that Feldman et al show how to use 2-matching to extract two
matchings in the type graph : the red matching and the blue matching.
This is later used in the Feldman et al algorithm.

This function overwrites vectors red and blue:

the vectors will be of size left_sz each, and record for each type i,
which offline vertex is matched to it in red and blue matching, respectively.
*/
void Graph::getFeldmanAdvice(vector<int> & red, vector<int> & blue) {

	// create flow network
	map<pair<int, int>, int> cap;
	map<pair<int, int>, int> flow;

	int s = adj.size(); // source
	int t = adj.size() + 1; // terminal

	for (int i = 0; i < left_sz; i++) {
		pair<int, int> p1 = make_pair(s, i);
		pair<int, int> p2 = make_pair(i, s);
		cap[p1] = 2;
		cap[p2] = flow[p1] = flow[p2] = 0;
	}

	for (int i = left_sz; i < (int)adj.size(); i++) {
		pair<int, int> p1 = make_pair(i, t);
		pair<int, int> p2 = make_pair(t, i);
		cap[p1] = 2;
		cap[p2] = flow[p1] = flow[p2] = 0;
	}

	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> p1 = make_pair(i, adj[i][j]);
			pair<int, int> p2 = make_pair(adj[i][j], i);
			cap[p1] = 1;
			cap[p2] = flow[p1] = flow[p2] = 0;
		}
	}

	vector<vector<int> > adj2 = adj; // for modified graph
	adj2.push_back(vector<int>()); // for s
	adj2.push_back(vector<int>()); // for t
	for (int i = 0; i < left_sz; i++) {
		adj2[i].push_back(s);
		adj2[s].push_back(i);
	}
	for (int i = left_sz; i < (int)adj.size(); i++) {
		adj2[i].push_back(t);
		adj2[t].push_back(i);
	}

	maxFlow(adj2, cap, flow, s, t);

	// extract advice from flow

	red = vector<int>(left_sz, -1);
	blue = vector<int>(left_sz, -1);

	// reduce to subgraph of positive flow
	vector<vector<int> > adj3(adj.size(), vector<int>());
	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			if (flow[edge] > 0) {
				adj3[i].push_back(adj[i][j]);
				adj3[adj[i][j]].push_back(i);
			}
		}
	}

	decomposeByFeldman(adj3, left_sz, blue, red);
}

/*
Runs Feldman matching on a given instance graph. The instance graph is 
specified by
(a) type graph (this object)
(b) vector of types -- online nodes

Feldman et al matching requires advice -- red matching and blue matching in 
the type graph.

greedy flag modifies the behavior of Feldman. If set to true, Feldman's 
algorithm runs in a greedy mode.
*/
vector<int> Graph::getFeldmanMatching(const vector<int> & types, 
	const vector<int> & red, const vector<int> &blue, bool greedy) {
	vector<int> result(types.size(), -1);
	vector<int> offline((int)adj.size() - left_sz, -1);
	vector<int> count(left_sz, 0);

	if (!greedy) {
		for (int i = 0; i < (int)types.size(); i++) {
			count[types[i]]++;
			if (count[types[i]] == 1 && blue[types[i]] != -1 && 
				offline[blue[types[i]] - left_sz] == -1) {
				result[i] = blue[types[i]];
				offline[blue[types[i]] - left_sz] = i;
			}
			else if (count[types[i]] == 2 && red[types[i]] != -1 && 
				offline[red[types[i]] - left_sz] == -1) {
				result[i] = red[types[i]];
				offline[red[types[i]] - left_sz] = i;
			}
		}
	}
	else {
		for (int i = 0; i < (int)types.size(); i++) {
			int best = -1;
			for (int j = 0; j < (int)adj[types[i]].size(); j++) {
				int nb = adj[types[i]][j];
				if (offline[nb - left_sz] == -1) {
					if (best == -1) {
						best = nb;
					}
					else if (blue[types[i]] == nb) {
						best = nb;
					}
					else if (red[types[i]] == nb && blue[types[i]] != best) {
						best = nb;
					}
				}
			}
			if (best != -1) {
				result[i] = best;
				offline[best - left_sz] = i;
			}
		}
	}

	return result;
}

int Graph::countMatchingSize(const vector<int> & matching) {
	int result = 0;
	for (int i = 0; i < (int)matching.size(); i++)
		if (matching[i] != -1)
			result++;
	return result;
}

/*
Creates an instance graph out of a given type graph and a vector of types.
To be called from a type graph.
*/
Graph Graph::createInstanceGraph(const vector<int> & types) {
	int leftSide = types.size();
	int rightSide = adj.size() - left_sz;

	Graph g(leftSide + rightSide);
	g.setLeftSize(leftSide);
	for (int i = 0; i < (int)types.size(); i++) {
		for (int j = 0; j < (int)adj[types[i]].size(); j++) {
			g.addEdge(i, adj[types[i]][j] - left_sz + leftSide);
		}
	}
	return g;
}

/*
To be run on a type graph. Estimates a fractional OPT by a Monte Carlo 
simulation. For rep number of times, generate an iid instance from a given 
type graph, where the number of samples is the same as left_sz (i.e., all 
arrival rates are 1). Solves the iid instance by using greedy+2ndpass+opt.
Records for each edge fraction of time it was used.
*/
vector<vector<pair<double, int> > > Graph::estimateFractionalOPTIIDMC(int rep) {
	map<pair<int, int>, double> frac;
	for (int i = 0; i < left_sz; i++)
		for (int j = 0; j < (int)adj[i].size(); j++)
			frac[make_pair(i, adj[i][j])] = 0.0;
	for (int z = 0; z < rep; z++) {
		vector<int> types = iid2(left_sz, left_sz);
		Graph g = createInstanceGraph(types);
		vector<int> greedySoln = g.greedy();
		vector<int> opt = g.secondPass(greedySoln);
		g.maxMatching(opt);
		for (int i = 0; i < left_sz; i++) {
			if (opt[i] != -1)
				frac[make_pair(types[i], opt[i])] += 1.0;
		}
	}

	vector<vector<pair<double, int> > > res(left_sz, 
		vector<pair<double, int> >());
	for (int i = 0; i < left_sz; i++)
		for (int j = 0; j < (int)adj[i].size(); j++)
			res[i].push_back(make_pair(frac[make_pair(i, adj[i][j])] / rep, 
				adj[i][j]));
	return res;
}

void printManshadi(vector<vector<pair<double, int> > > & frac) {
	for (int i = 0; i < (int)frac.size(); i++) {
		cout << i << ": ";
		for (int j = 0; j < (int)frac[i].size(); j++) {
			cout << "(" << frac[i][j].first << "," << frac[i][j].second << ") ";
		}
		cout << endl;
	}
}

/*
Runs Manshadi et al matching on a given instance graph. The instance graph is 
specified by
(a) type graph (this object)
(b) vector of types -- online nodes

Manshadi et al matching requires advice -- optimal fractional matching, which 
can be obtained by running estimateFractionalOPTIIDMC.

greedy flag modifies the behavior of Manshadi. If set to true, Manshadi's
algorithm runs in a greedy mode.
*/
vector<int> Graph::getManshadiMatching(const vector<int> & types, 
	const vector<vector<pair<double, int> > > & frac, bool greedy) {
	vector<int> result(types.size(), -1);
	vector<int> offline((int)adj.size() - left_sz, -1);

	vector<vector<pair<double, int> > > z1 = frac, z2;
	for (int i = 0; i < (int)z1.size(); i++) {
		double total = 0.0;
		for (int j = 0; j < (int)z1[i].size(); j++) {
			total += z1[i][j].first;
		}
		if (total < 1.0) {
			z1[i].push_back(make_pair(1.0 - total, -1));
		}
		sort(z1[i].rbegin(), z1[i].rend());
		z2.push_back(z1[i]);

		pair<double, int> temp = z2[i].front();
		z2[i].erase(z2[i].begin());
		z2[i].push_back(temp);

		double total1 = 0.0;
		double total2 = 0.0;
		for (int j = 0; j < (int)z1[i].size(); j++) {
			total1 += z1[i][j].first;
			z1[i][j].first = total1;

			total2 += z2[i][j].first;
			z2[i][j].first = total2;
		}
	}

	if (DEBUG_LEVEL > 3) {
		cout << "z1: " << endl;
		printManshadi(z1);

		cout << "-------" << endl;
		cout << "z2: " << endl;
		printManshadi(z2);
		cout << "-------" << endl;
	}

	//random_device rd;
	//mt19937 rng(rd());
	uniform_real_distribution<double> rand_sample(0, 1);

	for (int i = 0; i < (int)types.size(); i++) {
		double r = rand_sample(rng);
		pair<double, int> val = make_pair(r, -1);
		int f = (*upper_bound(z1[types[i]].begin(), 
			z1[types[i]].end(), val)).second;
		int s = (*upper_bound(z2[types[i]].begin(), 
			z2[types[i]].end(), val)).second;
		if (f != -1 && offline[f - left_sz] == -1) {
			result[i] = f;
			offline[f - left_sz] = i;
		}
		else if (s != -1 && offline[s - left_sz] == -1) {
			result[i] = s;
			offline[s - left_sz] = i;
		}
		else if (greedy) {
			for (int j = 0; j < (int)adj[types[i]].size(); j++) {
				if (offline[adj[types[i]][j] - left_sz] == -1) {
					result[i] = adj[types[i]][j];
					offline[adj[types[i]][j] - left_sz] = i;
					break;
				}
			}
		}
	}
	return result;
}


/*
This function is supposed to be run on a type graph.
Recall that Bahmani and Kapralov show how to modify Feldman et al 2-matching 
to extract two matchings in the type graph : the red matching and the blue 
matching. This is later used in the Feldman et al algorithm.

This function overwrites vectors red and blue:

the vectors will be of size left_sz each, and record for each type i,
which offline vertex is matched to it in red and blue matching, respectively.
*/
void Graph::getBahmaniAdvice(vector<int> & red, vector<int> & blue) {

	// create flow network
	map<pair<int, int>, int> cap;
	map<pair<int, int>, int> flow;

	int s = adj.size(); // source
	int t = adj.size() + 1; // terminal

	for (int i = 0; i < left_sz; i++) {
		pair<int, int> p1 = make_pair(i, t);
		pair<int, int> p2 = make_pair(t, i);
		cap[p1] = 2;
		cap[p2] = flow[p1] = flow[p2] = 0;
	}

	for (int i = left_sz; i < (int)adj.size(); i++) {
		pair<int, int> p1 = make_pair(s, i);
		pair<int, int> p2 = make_pair(i, s);
		cap[p1] = 2;
		cap[p2] = flow[p1] = flow[p2] = 0;
	}

	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> p1 = make_pair(i, adj[i][j]);
			pair<int, int> p2 = make_pair(adj[i][j], i);
			cap[p2] = 1;
			cap[p1] = flow[p1] = flow[p2] = 0;
		}
	}

	vector<vector<int> > adj2 = adj; // for modified graph
	adj2.push_back(vector<int>()); // for s
	adj2.push_back(vector<int>()); // for t
	for (int i = 0; i < left_sz; i++) {
		adj2[i].push_back(t);
		adj2[t].push_back(i);
	}
	for (int i = left_sz; i < (int)adj.size(); i++) {
		adj2[i].push_back(s);
		adj2[s].push_back(i);
	}

	maxFlow(adj2, cap, flow, s, t);

	/* find a cut */
	vector<int> visited(adj2.size(), -1);
	queue<int> q;
	q.push(s);
	visited[s] = 1;
	while (!q.empty()) {
		int cur = q.front();
		q.pop();
		for (int i = 0; i < (int)adj2[cur].size(); i++) {
			int to = adj2[cur][i];
			pair<int, int> edge = make_pair(cur, to);
			if (visited[to] == -1 && (cap[edge] - flow[edge] > 0)) {
				visited[to] = 1;
				q.push(to);
			}
		}
	}

	map<pair<int, int>, int> capS;
	map<pair<int, int>, int> flowS;
	vector<vector<int> > adjS = adj;

	adjS.push_back(vector<int>()); // s
	adjS.push_back(vector<int>()); // t

	for (int i = left_sz; i < (int)adj.size(); i++) {
		if (visited[i] == 1) {
			pair<int, int> edge1, edge2;
			if (flow[make_pair(s, i)] == 0) {
				edge1 = make_pair(s, i);
				edge2 = make_pair(i, s);
				adjS[s].push_back(i);
				adjS[i].push_back(s);
			}
			else if (flow[make_pair(s, i)] == 2) {
				edge1 = make_pair(i, t);
				edge2 = make_pair(t, i);
				adjS[t].push_back(i);
				adjS[i].push_back(t);
			}
			capS[edge1] = 1;
			capS[edge2] = 0;
			flowS[edge1] = flowS[edge2] = 0;
		}
	}
	for (int i = left_sz; i < (int)adj.size(); i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			pair<int, int> edgerev = make_pair(adj[i][j], i);
			if (visited[i] == 1 && visited[adj[i][j]] == 1) {
				if (flow[edge] > 0) {
					capS[edgerev] = 1;
					capS[edge] = 0;
					flowS[edge] = 0;
					flowS[edgerev] = 0;
				}
				else {
					capS[edge] = 1;
					capS[edgerev] = 0;
					flowS[edge] = 0;
					flowS[edgerev] = 0;
				}
			}
			else {
				capS[edge] = capS[edgerev] = flowS[edge] = flowS[edgerev] = 0;
			}
		}
	}
	maxFlow(adjS, capS, flowS, s, t);

	map<pair<int, int>, int> capT;
	map<pair<int, int>, int> flowT;
	vector<vector<int> > adjT = adj;

	adjT.push_back(vector<int>()); // s
	adjT.push_back(vector<int>()); // t

	for (int i = 0; i < left_sz; i++) {
		if (visited[i] == -1) {
			pair<int, int> edge1, edge2;
			if (flow[make_pair(i, t)] == 0) {
				edge1 = make_pair(s, i);
				edge2 = make_pair(i, s);
				adjT[s].push_back(i);
				adjT[i].push_back(s);
			}
			else if (flow[make_pair(i, t)] == 2) {
				edge1 = make_pair(i, t);
				edge2 = make_pair(t, i);
				adjT[t].push_back(i);
				adjT[i].push_back(t);
			}
			capT[edge1] = 1;
			capT[edge2] = 0;
			flowT[edge1] = flowT[edge2] = 0;
		}
	}
	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			pair<int, int> edgerev = make_pair(adj[i][j], i);
			if (visited[i] == -1 && visited[adj[i][j]] == -1) {
				if (flow[edgerev] > 0) {
					capT[edgerev] = 1;
					capT[edge] = 0;
					flowT[edge] = 0;
					flowT[edgerev] = 0;
				}
				else {
					capT[edge] = 1;
					capT[edgerev] = 0;
					flowT[edge] = 0;
					flowT[edgerev] = 0;
				}
			}
			else {
				capT[edge] = capT[edgerev] = flowT[edge] = flowT[edgerev] = 0;
			}
		}
	}
	maxFlow(adjT, capT, flowT, s, t);

	// reduce to subgraph of positive flow
	vector<vector<int> > adj3(adj.size(), vector<int>());
	for (int i = left_sz; i < (int)adj.size(); i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			if (flow[edge] + flowS[edge] - flowT[edge] > 0) {
				adj3[i].push_back(adj[i][j]);
				adj3[adj[i][j]].push_back(i);
			}
		}
	}

	red = vector<int>(left_sz, -1);
	blue = vector<int>(left_sz, -1);
	decomposeByFeldman(adj3, left_sz, blue, red);
}

/*
To be called on a type graph. Returns advice given by Jaillet and Lu 
preprocessing algorithm.
For each type, it gives a list of neighbors together with integers 1 or 2, 
such that the integers add up to at most 3.
*/
vector<vector<int> > Graph::getJailletAdvice() {
	map<pair<int, int>, int> cap;
	map<pair<int, int>, int> flow;

	int s = adj.size(); // source
	int t = adj.size() + 1; // terminal

	for (int i = 0; i < left_sz; i++) {
		pair<int, int> p1 = make_pair(t, i);
		pair<int, int> p2 = make_pair(i, t);
		cap[p2] = 3;
		cap[p1] = flow[p1] = flow[p2] = 0;
	}

	for (int i = left_sz; i < (int)adj.size(); i++) {
		pair<int, int> p1 = make_pair(i, s);
		pair<int, int> p2 = make_pair(s, i);
		cap[p2] = 3;
		cap[p1] = flow[p1] = flow[p2] = 0;
	}

	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> p1 = make_pair(i, adj[i][j]);
			pair<int, int> p2 = make_pair(adj[i][j], i);
			cap[p2] = 2;
			cap[p1] = flow[p1] = flow[p2] = 0;
		}
	}

	vector<vector<int> > adj2 = adj; // for modified graph
	adj2.push_back(vector<int>()); // for s
	adj2.push_back(vector<int>()); // for t
	for (int i = 0; i < left_sz; i++) {
		adj2[i].push_back(t);
		adj2[t].push_back(i);
	}
	for (int i = left_sz; i < (int)adj.size(); i++) {
		adj2[i].push_back(s);
		adj2[s].push_back(i);
	}

	maxFlow(adj2, cap, flow, s, t);

	vector<vector<int> > result(left_sz, vector<int>());
	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(adj[i][j], i);
			if (flow[edge] > 0) {
				for (int k = 0; k < flow[edge]; k++)
					result[i].push_back(adj[i][j]);
			}
		}
		while (result[i].size() < 3) result[i].push_back(-1);
	}
	return result;
}


/*
Runs Jaillet and Lu matching on a given instance graph. The instance graph is 
specified by
(a) type graph (this object)
(b) vector of types -- online nodes

Jaillet and Lu matching requires advice -- adjacency lists with probabilities
obtained by running getJailletAdvice().

greedy flag modifies the behavior of Feldman. If set to true, Jaillet's 
algorithm runs in a greedy mode.
*/
vector<int> Graph::getJailletMatching(const vector<int> &types,
	vector<vector<int> > & advice, bool greedy) {
	vector<int> res(types.size(), -1);
	vector<int> offline((int)adj.size() - left_sz, -1);

	for (int i = 0; i < (int)types.size(); i++) {
		int from = types[i];
		//random_shuffle(advice[from].begin(), advice[from].end());
		bool found = false;
		for (int j = 0; j < (int)advice[from].size(); j++) {
			int to = advice[from][j];
			if (to != -1 && offline[to - left_sz] == -1) {
				res[i] = to;
				offline[to - left_sz] = from;
				found = true;
				break;
			}
		}
		if (!found && greedy) {
			for (int j = 0; j < (int)adj[from].size(); j++) {

				int to = adj[from][j];
				if (offline[to - left_sz] == -1) {
					res[i] = to;
					offline[to - left_sz] = from;
					break;
				}
			}
		}
	}
	return res;
}

/*
Run this function on a type graph to get a solution to Brubach et al. LP.
The answer is returned in a map that gives the LP value for each edge.
The edges are not duplicated, the convention is that the smaller index in a
pair appears first.
*/
map<pair<int, int>, double> Graph::solveBrubachLP() {
	map<pair<int, int>, double> res;

	vector<int> ia, ja;
	vector<double> ea;
	map<pair<int, int>, int> varnames;
	vector<pair<int, int> > vars;

	ia.push_back(-1); ja.push_back(-1); ea.push_back(-1.0);
	vars.push_back(make_pair(-1, -1));

	glp_prob *blp = glp_create_prob();
	glp_set_obj_dir(blp, GLP_MAX);


	glp_term_out(GLP_OFF);

	//cerr << "************ SET ROW BOUNDS" << endl;

	int nrows = adj.size();
	glp_add_rows(blp, nrows);
	for (int i = 0; i < nrows; i++)
		glp_set_row_bnds(blp, i + 1, GLP_UP, 0.0, 1.0);

	//cerr << "************ SET COLUMN BOUNDS (1-1/e) AND OBJECTIVE" << endl;

	int ncols = getNumEdges();
	glp_add_cols(blp, ncols);
	for (int i = 0; i < ncols; i++) {
		glp_set_col_bnds(blp, i + 1, GLP_DB, 0.0, 1.0 - 1.0 / exp(1.0));
		glp_set_obj_coef(blp, i + 1, 1.0);
	}

	//cerr << "************ SET UP ADJACENCY MATRIX CONSTRAINTS" << endl;

	int n = left_sz;
	int m = (int)adj.size() - left_sz;
	int var = 1;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			ia.push_back(i + 1);
			ja.push_back(var);
			ea.push_back(1.0);

			ia.push_back(adj[i][j] + 1);
			ja.push_back(var);
			ea.push_back(1.0);

			varnames[make_pair(i, adj[i][j])] = var;
			vars.push_back(make_pair(i, adj[i][j]));
			var++;
		}
	}

	//cerr << "***************** SET UP 1-1/e^2 CONSTRAINTS" << endl;

	int currow = nrows;
	for (int j = left_sz; j < (int)adj.size(); j++) {
		for (int i1 = 0; i1 < (int)adj[j].size(); i1++) {
			for (int i2 = i1 + 1; i2 < (int)adj[j].size(); i2++) {
				pair<int, int> e1 = make_pair(adj[j][i1], j);
				pair<int, int> e2 = make_pair(adj[j][i2], j);
				glp_add_rows(blp, 1);
				currow++;
				glp_set_row_bnds(blp, currow, GLP_UP, 0.0, 1.0 - 1.0 / 
					(exp(1.0)*exp(1.0)));
				
				ia.push_back(currow);
				ja.push_back(varnames[e1]);
				ea.push_back(1.0);

				ia.push_back(currow);
				ja.push_back(varnames[e2]);
				ea.push_back(1.0);
			}
		}
	}

	//cerr << "************ LOAD MATRIX OF CONSTRAINTS" << endl;

	glp_load_matrix(blp, (int)ia.size() - 1, &ia[0], &ja[0], &ea[0]);

	//cerr << "************ RUN SIMPLEX" << endl;

	glp_simplex(blp, NULL);


	//cerr << "************ DONE" << endl;

	for (int i = 1; i <= ncols; i++) {
		double val = glp_get_col_prim(blp, i);
		res[vars[i]] = val;
	}

	glp_delete_prob(blp);
	glp_free_env();
	return res;
}

/*
Helper function that runs on an undirected graph in adjacency lists format given
by a. This function finds either a maximal path or cycle and returns it as a 
list of vertices.
*/
vector<int> Graph::findMaximalPathOrCycle(const vector<vector<int> > a) {
	vector<int> res;

	// try finding a vertex of degree 1 
	int st = -1;

	for (int i = 0; i < (int)a.size(); i++) {
		if (a[i].size() == 1) {
			st = i;
			break;
		}
		else if (st == -1 && a[i].size() > 0) {
			st = i;
		}
	}

	if (st != -1) {
		stack<int> S;
		vector<int> prev(a.size(), -1);
		prev[st] = st;
		S.push(st);
		while (!S.empty()) {
			int cur = S.top();
			S.pop();
			bool anyNbs = false;
			for (int j = 0; j < (int)a[cur].size(); j++) {
				int nb = a[cur][j];
				if (prev[nb] == -1) {
					S.push(nb);
					prev[nb] = cur;
					anyNbs = true;
					break;
				}
				else if (prev[cur] != nb) {
					// cycle!
					res.push_back(nb);
					do {
						res.push_back(cur);
						cur = prev[cur];
					} while (cur != nb);
					res.push_back(nb);
					return res;
				}
			}
			if (!anyNbs) {
				// maximal path!
				res.push_back(cur);
				do {
					res.push_back(prev[cur]);
					cur = prev[cur];
				} while (cur != prev[cur]);
				return res;
			}
		}
	}

	return res;
}


bool isFractional(double val) {
	return (min(ceil(val) - val, val - floor(val)) > 0.00001);
}

/*
Takes a fractional solution on the given bipartite graph and applies 
Ghandi et al. randomized rounding routine to convert it into an integral
solution.
*/
void Graph::doGhandiRounding(map<pair<int, int>, double> & frac) {
	vector<vector<int> > aux(adj.size(), vector<int>());
	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			if (isFractional(frac[edge])) {
				aux[i].push_back(adj[i][j]);
				aux[adj[i][j]].push_back(i);
			}
		}
	}
	while (true) {
		vector<int> twoM = findMaximalPathOrCycle(aux);
		if (twoM.size() == 0) break;

		assert(twoM.size() >= 2);

		double alpha = 1.0, beta = 1.0;

		for (int i = 0; i < (int)twoM.size() - 1; i++) {
			pair<int, int> e = make_pair(min(twoM[i], twoM[i + 1]), 
				max(twoM[i], twoM[i + 1]));
			if (i % 2 == 0) {
				alpha = min(alpha, ceil(frac[e]) - frac[e]);
				beta = min(beta, frac[e] - floor(frac[e]));
			}
			else {
				alpha = min(alpha, frac[e] - floor(frac[e]));
				beta = min(beta, ceil(frac[e]) - frac[e]);
			}
		}
		uniform_real_distribution<double> cur_rand(0.0, alpha + beta);
		bool roundFirst = (cur_rand(rng2) < beta);
		for (int i = 0; i < (int)twoM.size() - 1; i++) {
			pair<int, int> e = make_pair(min(twoM[i], twoM[i + 1]),
				max(twoM[i], twoM[i + 1]));
			if (i % 2 == 0) {
				if (roundFirst)
					frac[e] += alpha;
				else
					frac[e] -= beta;
			}
			else {
				if (roundFirst)
					frac[e] -= alpha;
				else
					frac[e] += beta;
			}
			if (!isFractional(frac[e])) {
				// delete edge
				int fr = e.first;
				int to = e.second;
				aux[fr].erase(find(aux[fr].begin(), aux[fr].end(), to));
				aux[to].erase(find(aux[to].begin(), aux[to].end(), fr));
			}
		}


	}
}

vector<int> intersection(vector<int> one, vector<int> two) {
	vector<int> result;
	sort(one.begin(), one.end());
	sort(two.begin(), two.end());
	set_intersection(one.begin(), one.end(), two.begin(), two.end(), 
		back_inserter(result));
	return result;
}

/*
Perform cycle breaking algorithm (Algorithm 7 from the paper) of Brubach et al.
frac specifies the rounded solution H from the paper. It is modified in place.
We actually work with an scaled version of H directly, i.e., it is 3H in the 
text.
*/
void Graph::doBrubachCycleBreaking(map<pair<int, int>, int> &rounded) {
	//construct a reduced graph
	vector<vector<int> > aux(adj.size(), vector<int>());
	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			if (rounded[edge] > 0) {
				aux[i].push_back(adj[i][j]);
				aux[adj[i][j]].push_back(i);
			}
		}
	}
	bool foundCycle;
	// find and destroy cycles
	do {
		foundCycle = false;

		for (int i = 0; i < left_sz; i++) {
			if (aux[i].size() == 2) { // potential C2 type
				pair<int, int> e1 = make_pair(i, aux[i][0]);
				pair<int, int> e2 = make_pair(i, aux[i][1]);
				if (rounded[e1] == 1 && rounded[e2] == 2) {
					pair<int, int> temp = e1;
					e1 = e2;
					e2 = temp;
				}
				if (rounded[e1] == 2 && rounded[e2] == 1) {
					vector<int> inter = intersection(aux[e1.second], 
						aux[e2.second]);
					for (int j = 0; j < (int)inter.size(); j++) {
						if (inter[j] != i) {
							pair<int, int> ep1 = make_pair(inter[j], e1.second);
							pair<int, int> ep2 = make_pair(inter[j], e2.second);
							if (rounded[ep1] == 1 && rounded[ep2] == 1) {
								foundCycle = true;
								rounded[e1] = 1;
								rounded[ep1] = 2;
								rounded[e2] = 2;
								rounded[ep2] = 0;

								// delete an edge:
								int fr = ep2.first;
								int to = ep2.second;
								aux[fr].erase(find(aux[fr].begin(),
									aux[fr].end(), to));
								aux[to].erase(find(aux[to].begin(), 
									aux[to].end(), fr));

								break;
							}
						}
					}
				}
			}
		}
		if (!foundCycle) {
			// try to find C3 style cycles
			for (int i = 0; i < left_sz; i++) {
				for (int j1 = 0; j1 < (int)aux[i].size(); j1++) {
					for (int j2 = j1 + 1; j2 < (int)aux[i].size(); j2++) {
						pair<int, int> e1 = make_pair(i, aux[i][j1]);
						pair<int, int> e2 = make_pair(i, aux[i][j2]);
						if (rounded[e1] == 1 && rounded[e2] == 1) {
							vector<int> inter = intersection(aux[aux[i][j1]],
								aux[aux[i][j2]]);
							for (int k = 0; k < (int)inter.size(); k++) {
								if (inter[k] != i) {
									pair<int, int> ep1 = make_pair(inter[k],
										aux[i][j1]);
									pair<int, int> ep2 = make_pair(inter[k],
										aux[i][j2]);
									if (rounded[ep1] == 1 && rounded[ep2] == 1) {
										foundCycle = true;
										rounded[e1] = 2;
										rounded[ep2] = 2;
										rounded[e2] = 0;
										rounded[ep1] = 0;

										// delete two edges
										int fr = e2.first;
										int to = e2.second;
										aux[fr].erase(find(aux[fr].begin(), 
											aux[fr].end(), to));
										aux[to].erase(find(aux[to].begin(), 
											aux[to].end(), fr));
										fr = ep1.first;
										to = ep1.second;
										aux[fr].erase(find(aux[fr].begin(),
											aux[fr].end(), to));
										aux[to].erase(find(aux[to].begin(),
											aux[to].end(), fr));

										goto out;
									}
								}
							}
						}
					}
				}
			}
		out:;
		}
	} while (foundCycle);
}


/*
Takes a rounded H with values in {0,1,2,3} and returns a fractional H with 
values in [0,1] as given by the Brubach et al (page 19 of the paper)
*/
map<pair<int, int>, double> Graph::doBrubach2ndTransform(
	map<pair<int, int>, int> &rounded) {
	map<pair<int, int>, double> res;
	vector<int> offlineSums((int)adj.size() - left_sz, 0);

	double x1 = 0.2744;
	double x2 = 0.15877;

	vector<vector<int> > aux(adj.size(), vector<int>());

	for (int i = 0; i < left_sz; i++) {
		for (int j = 0; j < (int)adj[i].size(); j++) {
			pair<int, int> edge = make_pair(i, adj[i][j]);
			res[edge] = 0.0;
			offlineSums[adj[i][j] - left_sz] += rounded[edge];
			if (rounded[edge] > 0) {
				aux[i].push_back(adj[i][j]);
				aux[adj[i][j]].push_back(i);
			}
		}
	}

	for (int i = 0; i < left_sz; i++) {
		if (aux[i].size() == 1) {
			pair<int, int> edge = make_pair(i, aux[i][0]);
			res[edge] = (double)rounded[edge] / 3.0;
		}
		else if (aux[i].size() == 2) {
			pair<int, int> e1 = make_pair(i, aux[i][0]);
			pair<int, int> e2 = make_pair(i, aux[i][1]);
			if (rounded[e1] == 1 && rounded[e2] == 1) {
				res[e1] = res[e2] = 1.0 / 3.0;
				continue;
			}
			if (rounded[e2] == 2 && rounded[e1] == 1) {
				pair<int, int> temp = e1;
				e1 = e2;
				e2 = temp;
			}
			if (rounded[e1] == 2 && rounded[e2] == 1) {
				int fat = offlineSums[e1.second - left_sz];
				int thin = offlineSums[e2.second - left_sz];
				double nfat, nthin;
				if (fat == 3) {
					if (thin == 1) { // case 1
						nfat = 0.9;
						nthin = 0.1;
					}
					else if (thin == 2) { // case 2
						nfat = 0.85;
						nthin = 0.15;
					}
					else if (thin == 3) {
						if (aux[e2.second].size() == 2) { // case 11
							nfat = 1.0 - x1;
							nthin = x1;
						}
						else if (aux[e2.second].size() == 3) { // case 12
							nfat = 1.0 - x2;
							nthin = x2;
						}
						else
							assert(false);
					}
					else {
						assert(false);
					}
				}
				else if (fat == 2) {
					if (thin == 3) { // case 3
						nfat = 0.6;
						nthin = 0.4;
					}
					else if (thin == 1) { // case	8
						nfat = 0.75;
						nthin = 0.25;
					}
					else if (thin == 2) { // case 9
						nfat = 0.7;
						nthin = 0.3;
					}
					else {
						assert(false);
					}
				}
				res[e1] = nfat;
				res[e2] = nthin;
			}
		}
		else if (aux[i].size() == 3) {
			vector<pair<int, int> > nbs;
			nbs.push_back(make_pair(offlineSums[aux[i][0] - left_sz], 
				aux[i][0]));
			nbs.push_back(make_pair(offlineSums[aux[i][1] - left_sz], 
				aux[i][1]));
			nbs.push_back(make_pair(offlineSums[aux[i][2] - left_sz],
				aux[i][2]));
			sort(nbs.begin(), nbs.end());

			double r1, r2, r3;

			if (nbs[0].first == 1 && nbs[1].first == 1 && nbs[2].first == 1) {
				r1 = 1.0 / 3.0;
				r2 = 1.0 / 3.0;
				r3 = 1.0 / 3.0;
			}
			else if (nbs[0].first == 1 && nbs[1].first == 1 && 
				nbs[2].first == 2) {
				r1 = 1.0 / 3.0;
				r2 = 1.0 / 3.0;
				r3 = 1.0 / 3.0;
			}
			else if (nbs[0].first == 1 && nbs[1].first == 1 && 
				nbs[2].first == 3) { // case 7
				r1 = 0.1;
				r2 = 0.1;
				r3 = 0.8;
			}
			else if (nbs[0].first == 1 && nbs[1].first == 2 && 
				nbs[2].first == 2) {
				r1 = 1.0 / 3.0;
				r2 = 1.0 / 3.0;
				r3 = 1.0 / 3.0;
			}
			else if (nbs[0].first == 1 && nbs[1].first == 2 &&
				nbs[2].first == 3) { // case 6
				r1 = 0.15;
				r2 = 0.2;
				r3 = 0.65;
			}
			else if (nbs[0].first == 1 && nbs[1].first == 3 &&
				nbs[2].first == 3) { // case 4
				r1 = 0.1;
				r2 = 0.45;
				r3 = 0.45;
			}
			else if (nbs[0].first == 2 && nbs[1].first == 2 && 
				nbs[2].first == 2) {
				r1 = 1.0 / 3.0;
				r2 = 1.0 / 3.0;
				r3 = 1.0 / 3.0;
			}
			else if (nbs[0].first == 2 && nbs[1].first == 2 && 
				nbs[2].first == 3) { // case 10
				r1 = 0.25;
				r2 = 0.25;
				r3 = 0.5;
			}
			else if (nbs[0].first == 2 && nbs[1].first == 3 && 
				nbs[2].first == 3) { // case 5
				r1 = 0.2;
				r2 = 0.4;
				r3 = 0.4;
			}
			else if (nbs[0].first == 3 && nbs[1].first == 3 &&
				nbs[2].first == 3) {
				r1 = 1.0 / 3.0;
				r2 = 1.0 / 3.0;
				r3 = 1.0 / 3.0;
			}
			else {
				assert(false);
			}

			res[make_pair(i, nbs[0].second)] = r1;
			res[make_pair(i, nbs[1].second)] = r2;
			res[make_pair(i, nbs[2].second)] = r3;
		}
	}

	return res;
}

/*
Returns an adjacency list format version of Brubach advice

res[i] records a list of pair [H'(u), u] corresponding to online vertex i.
*/
vector<vector<pair<double, int> > > Graph::getBrubachAdvice() {
	//cerr << "------- SOLVING LP" << endl;
	// SOLVE LP
	map<pair<int, int>, double> lpsol = solveBrubachLP();

	//cerr << "------- MULTIPLY BY 3 AND ROUND" << endl;
	// MULTIPLY BY 3 AND ROUND
	for (auto it = lpsol.begin(); it != lpsol.end(); it++)
		it->second = 3.0*(it->second);
	doGhandiRounding(lpsol);

	//cerr << "------- CONVERT TO INTEGERS" << endl;
	// CONVERT TO INTEGERS
	map<pair<int, int>, int> lpsolint;
	for (auto it = lpsol.begin(); it != lpsol.end(); it++) {
		lpsolint[it->first] = (int)(0.5 + it->second);
	}

	//cerr << "------- BREAK CYCLES" << endl;
	// BREAK CYCLES
	doBrubachCycleBreaking(lpsolint);

	//cerr << "------- SECOND TRANSFORM" << endl;
	// PERFORM FINAL MODIFICATION
	map<pair<int, int>, double> modified = doBrubach2ndTransform(lpsolint);

	//cerr << "------- OUTPUT FORMAT" << endl;
	// CONVERT TO OUTPUT FORMAT
	vector<vector<pair<double, int> > > res(left_sz, 
		vector<pair<double, int> >());
	for (auto it = modified.begin(); it != modified.end(); it++) {
		if (it->second > 0.00001)
			res[it->first.first].push_back(make_pair(it->second, 
				it->first.second));
	}
	return res;
}

/*
Returns a matching constrcuted by Brubach et al algorithm. Requires Brubach et 
al advice that can be obtained by running getBrubachAdvice();
*/
vector<int> Graph::getBrubachMatching(const vector<int> & types,
	vector<vector<pair<double, int> > > &advice, bool greedy) {
	vector<int> res(types.size(), -1);
	vector<int> offline((int)adj.size() - left_sz, -1);

	for (int i = 0; i < (int)types.size(); i++) {
		int from = types[i];
		int match = -1;

		if (advice[from].size() == 1) {
			int to = advice[from][0].second;
			if (offline[to - left_sz] == -1) {
				match = to;
				offline[to - left_sz] = from;
			}
		}
		else if (advice[from].size() == 2) {
			double tot = advice[from][0].first + advice[from][1].first;
			uniform_real_distribution<double> cur_rand(0.0, tot);
			if (cur_rand(rng2) > advice[from][0].first) {
				swap(advice[from][0], advice[from][1]);
			}
			for (int j = 0; j < 2; j++) {
				int to = advice[from][j].second;
				if (offline[to - left_sz] == -1) {
					match = to;
					offline[to - left_sz] = from;
					break;
				}
			}
		}
		else if (advice[from].size() == 3) {
			int first[] = { 0,0,1,1,2,2 };
			int second[] = { 1,2,0,2,1,0 };
			int third[] = { 2,1,2,0,0,1 };
			double vals[7];
			for (int z = 0; z < 6; z++) {
				double hi = advice[from][first[z]].first;
				double hj = advice[from][second[z]].first;
				double hk = advice[from][third[z]].first;
				vals[z] = (hi*hj) / (hj + hk);
			}
			for (int z = 1; z < 6; z++)
				vals[z] += vals[z - 1];
			vals[6] = 1000000.0;
			uniform_real_distribution<double> cur_rand(0.0, vals[5]);
			double curnum = cur_rand(rng2);
			int ind = 0;
			while (curnum > vals[ind]) {
				ind++;
			}
			assert(ind < 6);
			vector<int> order;
			order.push_back(first[ind]);
			order.push_back(second[ind]);
			order.push_back(third[ind]);
			for (int z = 0; z < 3; z++) {
				int to = advice[from][order[z]].second;
				if (offline[to - left_sz] == -1) {
					match = to;
					offline[to - left_sz] = from;
					break;
				}
			}
		}

		if (match == -1 && greedy) {
			for (int j = 0; j < (int)adj[from].size(); j++) {
				int to = adj[from][j];
				if (offline[to - left_sz] == -1) {
					match = to;
					offline[to - left_sz] = from;
					break;
				}
			}
		}
		res[i] = match;
	}
	return res;
}